#include "MapRenderer.h"
#include "CudaTexture.cuh"
#include <algorithm/rayTracing/dataLoader/SceneConfiguration.h>
#include <algorithm/rayTracing/dataLoader/SceneField.h>
#include <algorithm/rayTracing/dataLoader/SceneLoader.h>
#include <algorithm/rayTracing/rayTracingPipeline/RayTracingPipeline.h>


#include <QDebug>
#include <QMatrix4x4>
#include <QOpenGLBuffer>
#include <QOpenGLContext>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QQuickItem>
#include <QQuickWindow>
#include <QRunnable>
#include <QSGRendererInterface>
#include <QThread>
#include <QVector3D>

#include <bit>

namespace solar::internal
{
    static constexpr const char map_shader_vsh[] = R"(
// vertex shader

uniform mat4 mvpmatrix;          // mvp矩阵
attribute highp vec3 position;   // 输入的顶点位置
attribute highp vec3 color;      // 输入颜色
attribute highp vec2 a_texcoord; // 纹理坐标

varying highp vec3 v_color;
varying highp vec2 v_texcoord;

void main() {
  gl_Position = mvpmatrix * vec4(position, 1.0);
  v_color = color;
  v_texcoord = a_texcoord;
}
)";

    static constexpr const char map_shader_fsh[] = R"(
// fragment shader

// out vec4 FragColor;     // 输出颜色

uniform sampler2D texture;

varying highp vec3 v_color;        // 从顶点着色器传来的属性
varying highp vec2 v_texcoord;

void main()
{
    // gl_FragColor = vec4(v_color, 1.0);
    // gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
    gl_FragColor = texture2D(texture, v_texcoord);
}
)";
} // namespace solar::internal

/**
 * 貌似不可以直接使用
 */
using namespace solar;
using namespace solar::cuda::inter_op;

MapRenderer::MapRenderer()
    : program_(nullptr), window_(nullptr),
      vertex_buffer_(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer)),
      index_buffer_(new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer)),
      color_buffer_(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer)),
      texcoord_buffer_(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer)), texture_(nullptr),
      cuda_texture_(new cuda::CudaTexture())
{

    // cudatexture->setColor();
}
MapRenderer::~MapRenderer()
{
    delete program_;
    program_ = nullptr;
    // if(texture_) delete texture_ ;
    // texture_ = nullptr;

    // glDeleteVertexArrays(1, &VAO);       // VAO貌似不适用
    // glDeleteBuffers(1, &VBO);
    // glDeleteBuffers(1, &EBO);
    vertex_buffer_->destroy();
    index_buffer_->destroy();
    color_buffer_->destroy();
    texcoord_buffer_->destroy();
}

void MapRenderer::init()
{
    if (program_ == nullptr)
    {
        qDebug() << "init";

        QSGRendererInterface* rif = window_->rendererInterface();
        Q_ASSERT(rif->graphicsApi() == QSGRendererInterface::OpenGL);

        initializeOpenGLFunctions();

        program_ = new QOpenGLShaderProgram();
        program_->addShaderFromSourceCode(QOpenGLShader::Vertex, internal::map_shader_vsh);
        program_->addShaderFromSourceCode(QOpenGLShader::Fragment, internal::map_shader_fsh);
        // program_->addShaderFromSourceFile(QOpenGLShader::Vertex,
        //                                   "/home/liu/Git_resposity/solar/solar_software/solar_2/"
        //                                   "source/test_folder/test_shader/map_shader.vsh");
        // program_->addShaderFromSourceFile(QOpenGLShader::Fragment,
        //                                   "../source/test_folder/test_shader/map_shader.fsh");

        program_->bindAttributeLocation("mvpmatrix", 0);
        program_->bindAttributeLocation("position", 1);
        program_->bindAttributeLocation("color", 2);
        program_->link();

        // /*
        //     可以先在init()里先把数据赋上
        //
        // */
        // program_->bind();

        // glGenVertexArrays(1, &VAO);
        // glGenBuffers(1, &VBO);
        // glGenBuffers(1, &EBO);

        // glBindVertexArray(VAO);
        // glBindBuffer(GL_ARRAY_BUFFER, VBO);
        // glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

        // glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
        // glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

        // program_->release();

        const GLfloat vertices[] = {0.5F,  0.5F,  0.0F, 0.5F,  -0.5F, 0.0F,
                                    -0.5F, -0.5F, 0.0F, -0.5F, 0.5F,  0.0F};
        // const GLfloat length = 10.0f;
        // const GLfloat vertices[] =
        // {
        //     length, -length, length,
        //     length, -length, -length,
        //     -length, -length, -length,
        //     -length, -length, length,
        //     length, length, length,
        //     length, length, -length,
        //     -length, length, -length,
        //     -length, length, length
        // };
        vertex_buffer_->setUsagePattern(QOpenGLBuffer::StaticDraw);
        vertex_buffer_->create();
        vertex_buffer_->bind();
        vertex_buffer_->allocate(vertices, sizeof(vertices));

        const GLfloat texcoords[] = {1, 1, 1, 0, 0, 0, 0, 1};
        texcoord_buffer_->setUsagePattern(QOpenGLBuffer::StaticDraw);
        texcoord_buffer_->create();
        texcoord_buffer_->bind();
        texcoord_buffer_->allocate(texcoords, sizeof(texcoords));

        const GLfloat colors[] = {
            1.0f, 0.0f, 0.0f, // 右上
            0.0f, 1.0f, 0.0f, // 右下
            0.0f, 0.0f, 1.0f, // 左下
            1.0f, 1.0f, 0.0f  // 左上
        };
        color_buffer_->setUsagePattern(QOpenGLBuffer::StaticDraw);
        color_buffer_->create();
        color_buffer_->bind();
        color_buffer_->allocate(colors, sizeof(colors));

        GLubyte indices[] = {0, 1, 2, 0, 2, 3};
        index_buffer_->setUsagePattern(QOpenGLBuffer::StaticDraw);
        index_buffer_->create();
        index_buffer_->bind();
        index_buffer_->allocate(indices, sizeof(indices));
    }
    /**
     * TODO:: CORRELATE WITH CUDA STORAGE
     */

    // if(! texture_)
    // {
    //     // cudatexture = new CudaTexture();
    //     // cudatexture->generateTexture();

    //     // // cudatexture->generateTexture();
    //     // texture_ = cudatexture->getTexture();

    //     texture_ = new QOpenGLTexture(QImage("./flux_map.png").mirrored());
    //     texture_->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
    //     texture_->setMagnificationFilter(QOpenGLTexture::Linear);
    //     texture_->setWrapMode(QOpenGLTexture::Repeat);
    // }
}

void MapRenderer::loadTexture()
{
    // // texture_ = QOpenGLTexture(QImage("./flux_map.png").mirrored());
    // // texture_->destroy();
    // // texture_->create();
    // // texture_->setData(QImage("./flux_map.png").mirrored());
    // // texture_->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
    // // texture_->setMagnificationFilter(QOpenGLTexture::Linear);
    // // texture_->setWrapMode(QOpenGLTexture::Repeat);
    // // qDebug() <<"MapRenderer reloadTexture";
    // if(!visible_) return;
    // if(texture_) return;
    // // cudatexture->generateTextureViaCudaDataPtr();
    // qDebug() <<" MapRenderer::set texture---begin";
    // cudatexture->generateTexture();
    // texture_ = cudatexture->getTexture();
    // qDebug() <<" MapRenderer::set texture---end";
}

/**
 * 调用CudaTexture中的方法，从float* data生成新的纹理图片，再赋值给这里的texture_
 */
void MapRenderer::generateTexture(Uchar4* fluxmap_data, Int2 resolution)
{
    qDebug() << " MapRenderer::generate texture";
    set_cuda_ = true;
    cuda_texture_->setCudaPara(std::bit_cast<uchar4*>(fluxmap_data), {resolution.x, resolution.y});
    visible_ = true;
}

/**
 * texture的计算可以放到raytracing完成之后，点开datavisuallization时，不必等到点击plot按钮之后再开始
 */

void MapRenderer::paint()
{
    glViewport(x_, y_, viewport_size_.width(), viewport_size_.height());

    if (!visible_)
    {
        window_->beginExternalCommands();
        glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glDisable(GL_DEPTH_TEST);
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ZERO);
        window_->endExternalCommands();
        return;
    }
    // if(!texture_) return;
    if (texture_ == nullptr)
    {
        /*
        qDebug() <<" MapRenderer::paint !texture---begiin";
        // cudatexture->generateTexture();

        auto start_time = std::chrono::high_resolution_clock::now();
        auto end_time = start_time;
        long long elapsed;

        cudatexture->generateTextureViaCudaDataPtr();
        texture_ = cudatexture->getTexture();
        // generateTextureViaCudaDataPtr()

        end_time = std::chrono::high_resolution_clock::now();
        elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end_time -
        start_time).count(); qDebug() << " it took " << elapsed << " microseconds to calculate cuda
        texture." ;

        qDebug() <<" MapRenderer::paint !texture---end";
        */
        // texture_->create();
        // texture_->bind(cudatexture->getTextureId());
    }
    if (!cuda_texture_->finished())
    {
        cuda_texture_
            ->generateTextureViaRayTracingPipeline(); // 关于openGL的操作只能在rendering线程操作，否则会出现未知错误
    }

    window_->beginExternalCommands();

    qDebug() << "paint";
    qDebug() << x_ << " " << y_ << " " << viewport_size_.width() << " " << viewport_size_.height();
    // if(x_ <= 0 || y <= 0 || )

    // /**
    //  * TODO::每次paint都要加载一次纹理，浪费性能
    // */
    // // reloadTexture();

    // 做剪切，只有scissor rehion中的内容可以保留
    // glEnable(GL_SCISSOR_TEST);
    // glScissor(x_, y_, viewport_size_.width(), viewport_size_.height());

    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // qDebug() <<"111";
    program_->bind();

    // glBindVertexArray(VAO);
    // glBindBuffer(GL_ARRAY_BUFFER, VBO);
    // glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

    // glVertexAttribPointer(1,3,GL_FLOAT, 6*sizeof(float), (void*)0);
    // glEnableVertexAttribArray(1);

    // glVertexAttribPointer(2, 3, GL_FLOAT, 6*sizeof(float), (void*)(3*sizeof(float)));
    // glEnableVertexAttribArray(2);

    // qDebug() <<"222";

    vertex_buffer_->bind();
    auto pos_loc = program_->attributeLocation("position");
    program_->enableAttributeArray(pos_loc);
    program_->setAttributeBuffer(pos_loc, GL_FLOAT, 0, 3, 0);

    texcoord_buffer_->bind();
    auto tex_loc = program_->attributeLocation("a_texcoord");
    program_->enableAttributeArray(tex_loc);
    program_->setAttributeBuffer(tex_loc, GL_FLOAT, 0, 2, 0);

    color_buffer_->bind();
    auto col_loc = program_->attributeLocation("color");
    program_->enableAttributeArray(col_loc);
    program_->setAttributeBuffer(col_loc, GL_FLOAT, 0, 3, 0);

    index_buffer_->bind();
    // texture_->bind();

    // qDebug() <<"333";

    QMatrix4x4 mvp_matrix;
    mvp_matrix.setToIdentity();
    mvp_matrix.scale(2, 2);
    // mvp_matrix.scale(viewport_size_.width(), viewport_size_.height());
    program_->setUniformValue("mvpmatrix", mvp_matrix);

    // glDisable(GL_DEPTH_TEST);

    // glEnable(GL_BLEND);
    // glBlendFunc(GL_SRC_ALPHA, GL_ONE);

    // glEnable(GL_DEPTH_TEST);
    // glEnable(GL_CULL_FACE);
    // glFrontFace(GL_CW);

    // qDebug() <<"paint";
    // glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, Q_NULLPTR);

    // glDisable(GL_BLEND);
    // glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    // glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // glEnable(GL_DEPTH_TEST);
    // glEnable(GL_CULL_FACE);
    // glFrontFace(GL_CW);

    // qDebug() <<"444";

    glBindTexture(GL_TEXTURE_2D, cuda_texture_->getTextureId());

    // auto texture_id = RayTracingPipeline::result_data_->texture_id;
    // glBindTexture(GL_TEXTURE_2D, texture_id);

    // 这几句一定要加！！！！
    glDisable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ZERO);

    // qDebug() <<"555";

    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, Q_NULLPTR);
    // qDebug() <<"666";

    glBindTexture(GL_TEXTURE_2D, 0);

    // glBegin(GL_LINES);
    //     glColor3f(1.0, 0, 0);
    //     glVertex3f(-2,-2,0);
    //     glVertex3f(2,2,0);
    //     glVertex3f(1,-1,0);
    //     glVertex3f(-1,1,0);
    // glEnd();

    //     glBegin(GL_TRIANGLES);
    //    glColor3f(1.0,0.0,0.0);
    //    glVertex2f(0.0,1.0);
    //    glColor3f(0.0,1.0,0.0);
    //    glVertex2f(0.8,-0.5);
    //    glColor3f(0.0,0.0,1.0);
    //    glVertex2f(-0.8,-0.5);
    //     glEnd();

    program_->disableAttributeArray(1);
    program_->disableAttributeArray(2);
    vertex_buffer_->release();
    color_buffer_->release();
    index_buffer_->release();
    // texture_->release();
    program_->release();

    map_completed_ = true;
    // emit imageCompleted();

    window_->endExternalCommands();
}
// void MapRenderer::imageCompleted()
// {

// }
